﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using winS.winML;

namespace winS.Unity.Process
{
    /// <summary>
    /// Provides a default <see cref="IProcess"/> implementation.
    /// </summary>
    public class Process : IProcess
    {
        /// <summary>
        /// Called when Completed.
        /// </summary>
        public event Action onPlayCompleted { add { _onPlayCompleted += value; } remove { _onPlayCompleted -= value; } }

        /// <summary>
        /// Called when Finish(whether completed or stopped).
        /// </summary>
        internal event Action onFinished;

        public bool playing { get; private set; }

        [WinMLSerializable]
        public virtual StartStep startStep { get; set; } = new StartStep();
        [WinMLSerializable]
        public Dictionary<string, IStep> stepDictionary { get; set; } = new Dictionary<string, IStep>();
        [WinMLSerializable]
        public virtual EditorData editorData { get; set; } = new EditorData();

        /// <summary>
        /// Data passed to process.
        /// </summary>
        public object data { get; set; }

        protected Action _onPlayCompleted;

        private int coroutineCount;
        private CoroutineGroup coroutineGroup;

        private readonly List<IStep> onlyOnceStepList = new List<IStep>();

        public virtual IProcessPlayState Play()
        {
            return PlayFromStep(startStep);
        }
        public IProcessPlayState Play(object data)
        {
            this.data = data;
            return Play();
        }
        public void Stop()
        {
            if (!playing) return;
            playing = false;
            onFinished?.Invoke();
            coroutineGroup?.Stop();
        }

        public void AddStep(IStep step)
        {
            if (!stepDictionary.TryAdd(step.guid, step)) throw new Exception($"{nameof(Process)}.{nameof(AddStep)}: 添加Step {step.GetType().Name} 失败, Step已存在");
        }
        public void RemoveStep(IStep step)
        {
            if (stepDictionary.Remove(step.guid)) return;
            Debug.LogError($"{nameof(Process)}.{nameof(RemoveStep)}: 移除Step {step.GetType().Name} 失败, Step不存在! 这是一个致命错误, 请联系管理员");
        }
        public IProcessPlayState PlayFromStep(IStep step)
        {
            if (playing) throw new Exception($"{nameof(Process)}.{nameof(PlayFromStep)}: 不允许重复播放");
            coroutineCount = 0;
            playing = true;
            onlyOnceStepList.Clear();
            coroutineGroup = new CoroutineGroup();
            PlayStep(step);
            return new ProcessPlayState(this);
        }

        private void PlayStep(IStep step)
        {
            coroutineCount++;
            coroutineGroup.Start(PlayStepProcess(step));
        }
        private void PlaySteps(IEnumerable<IStep> steps)
        {
            foreach (IStep step in steps) PlayStep(step);
        }
        private IEnumerator PlayStepProcess(IStep step)
        {
            ProcessContext processContext = new ProcessContext(this, data);
            if (!step.isMute) yield return step.Execute(processContext);
            if (step.isOnlyOnceNextSteps)
            {
                if (onlyOnceStepList.Contains(step)) goto End;
                onlyOnceStepList.Add(step);
            }
            //在执行Step时, 可能有对Process的Stop操作, Stop操作停止当前协程组中的所有协程
            //但当代码在这里执行时, 如果我们不等待一帧(yield return null)
            //会导致协程无法在内部进行Stop检测, 因为代码是一次性全部跑完, 所以后续的Step依旧会被运行
            //为了在Stop后让协程触发内部检测, 必须等待一帧
            //当然, 也可以在调用 PlayStep(nextStep) 前判断当前Process是否已经暂停, 但等待一帧会更加优雅
            foreach (var nextStep in step.GetSubsequentSteps(processContext))
            {
                yield return null;
                PlayStep(stepDictionary[nextStep]);
            }
        End:
            coroutineCount--;
            if (coroutineCount == 0)
            {
                if (!playing) Debug.LogError("流程运行出现了严重错误, 请联系 win_shen@126.com");
                _onPlayCompleted?.Invoke();
                playing = false;
                onFinished?.Invoke();
            }
        }

        IEnumerator<IStep> IEnumerable<IStep>.GetEnumerator()
        {
            return stepDictionary.Values.GetEnumerator();
        }
        IEnumerator IEnumerable.GetEnumerator()
        {
            return stepDictionary.Values.GetEnumerator();
        }
    }
}